﻿using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Data.Entity;
using System.Data.Entity.Infrastructure;
using System.Linq;
using Infrastructure.DAL.SeedWork.Extension;
using Infrastructure.Repository.EntityFramework.Extension;

namespace Infrastructure.Repository.EntityFramework.UnitOfWork
{
    public class BaseEFUnitOfWork : DbContext, IQueryableUnitOfWork
    {
        public Dictionary<string, IBatchEntityConfiguration> BatchEntityConfigs { get; set; }

        private ConcurrentDictionary<string, object> _allSet { get; set; }

        #region constructor

        public BaseEFUnitOfWork(string nameOrConnectionString)
            : base(nameOrConnectionString)
        {
            _allSet = new ConcurrentDictionary<string, object>();
            BatchEntityConfigs = new Dictionary<string, IBatchEntityConfiguration>();
        }

        #endregion

        #region IQueryableUnitOfWork Members

        /// <summary>
        ///     额外处理了多线程重复创建的问题
        /// </summary>
        /// <typeparam name="TEntity"></typeparam>
        /// <returns></returns>
        public DbSet<TEntity> CreateSet<TEntity>()
            where TEntity : class
        {
            string key = typeof (TEntity).FullName;
            object result;
            if (!_allSet.TryGetValue(key, out result))
            {
                result = Set<TEntity>();
                _allSet.TryAdd(key, result);
            }
            return (DbSet<TEntity>) result;
            //return Set<TEntity>();
        }

        public void Attach<TEntity>(TEntity item)
            where TEntity : class
        {
            //attach and set as unchanged
            Entry(item).State = EntityState.Unchanged;
        }

        public void SetModified<TEntity>(TEntity item)
            where TEntity : class
        {
            //this operation also attach item in object state manager
            Entry(item).State = EntityState.Modified;
        }

        public void SetDetached<TEntity>(TEntity item) where TEntity : class
        {
            Entry(item).State = EntityState.Detached;
        }

        public void ApplyCurrentValues<TEntity>(TEntity original, TEntity current)
            where TEntity : class
        {
            //if it is not attached, attach original and set current values
            Entry(original).CurrentValues.SetValues(current);
        }

        public void Commit()
        {
            base.SaveChanges();
        }

        public void CommitAndRefreshChanges()
        {
            bool saveFailed;

            do
            {
                try
                {
                    base.SaveChanges();
                    saveFailed = false;
                }
                catch (DbUpdateConcurrencyException ex)
                {
                    saveFailed = true;

                    ex.Entries.ToList()
                        .ForEach(entry => entry.OriginalValues.SetValues(entry.GetDatabaseValues()));
                }
            } while (saveFailed);
        }

        public void RollbackChanges()
        {
            // set all entities in change tracker 
            // as 'unchanged state'
            ChangeTracker.Entries()
                .ToList()
                .ForEach(entry => entry.State = EntityState.Detached);
            //_allSet.Values
            //       .ToList()
            //       .ForEach(item => ((IObjectContextAdapter)this).ObjectContext.Refresh(RefreshMode.StoreWins, item));
        }

        public IEnumerable<TEntity> ExecuteQuery<TEntity>(string sqlQuery, params object[] parameters)
        {
            return Database.SqlQuery<TEntity>(sqlQuery, parameters);
        }

        public int ExecuteCommand(string sqlCommand, params object[] parameters)
        {
            return Database.ExecuteSqlCommand(sqlCommand, parameters);
        }

        #endregion
    }
}